#include <net/flow_offload.h>
#include <net/neighbour.h>
#include <net/arp.h>
#include <net/ip.h>
#include <linux/etherdevice.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include "nveth_test_flow.h"
#include "nveth.h"

struct nf_flowtable* gnveth_flowtable = 0;

static int  nveth_fill_flow_dmac(struct flow_offload_tuple* flow)
{
	struct net_device *dev;
	struct neighbour *n;
	u8 nud_state;
	int ifindex = flow->out.ifidx;

	dev = dev_get_by_index(&init_net, ifindex);
	if (!dev) {
		printk("no device found for ifindex %d\n", ifindex);
		return -1;
	}

	//n = neigh_lookup(&arp_tbl, &flow->dst_v4, dev);
	n = __ipv4_neigh_lookup(dev, flow->dst_v4.s_addr);
	if (!n)
		return -1;

	read_lock_bh(&n->lock);
	nud_state = n->nud_state;
	ether_addr_copy(flow->out.h_dest, n->ha);
	read_unlock_bh(&n->lock);

	neigh_release(n);

	if (!(nud_state & NUD_VALID))
		return -1;

	return 0;
}

static int  nveth_fill_flow_smac(struct flow_offload_tuple* flow)
{
	struct net_device *dev;
	struct neighbour *n;
	int ifindex = flow->out.ifidx;

	dev = dev_get_by_index(&init_net, ifindex);
	if (!dev) {
		printk("no device found for ifindex %d\n", ifindex);
		return -1;
	}

	ether_addr_copy(flow->out.h_source, dev->dev_addr);

	return 0;
}


static  LIST_HEAD(nveth_block_ft_cb_list);

static int nveth_setup_ft_cb(enum tc_setup_type, void *type_data, void *cb_priv)
{
	struct flow_offload *flow;
	struct flow_offload_tuple_rhash *tuplehash;
	struct flow_cls_offload *f = type_data;
	struct flow_offload_tuple *tuple = (struct flow_offload_tuple*)f->cookie;
	struct nveth_test_flow*  testflow = (struct nveth_test_flow*)cb_priv;
	int dir;
	struct flow_offload_tuple *origft, *replyft;

	if (!testflow->flowtable) {
		printk("no flowtable\n");
		return -1;
	}

	tuplehash = flow_offload_lookup(testflow->flowtable, tuple);
	if (tuplehash) {
		dir = tuplehash->tuple.dir;
		flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);

		origft = &flow->tuplehash[0].tuple;
		replyft = &flow->tuplehash[1].tuple;

		printk("origft: src :%pI4, dst:%pI4, sport:%d, dport:%d, smac:%pM, dmac:%pM, iifidx:%d, oif:%d",
				&origft->src_v4, &origft->dst_v4, origft->src_port, origft->dst_port,
				origft->out.h_source, origft->out.h_dest, origft->iifidx, origft->out.ifidx);

		printk("replyft: src :%pI4, dst:%pI4, sport:%d, dport:%d, smac:%pM, dmac:%pM, iifidx:%d, oif:%d",
				&replyft->src_v4, &replyft->dst_v4, replyft->src_port, replyft->dst_port,
				replyft->out.h_source, replyft->out.h_dest, replyft->iifidx, replyft->out.ifidx);
	}

	return 0;
}

int nveth_testflow_setup_tc(struct net_device *dev, enum tc_setup_type type, void *type_data)
{
	struct nveth_priv *priv = netdev_priv(dev);
	struct nveth_test_flow *testflow = &priv->testflow;
	struct flow_block_offload *f = type_data;

	/*get the flowtable which is configured with offload flag*/
	if (f->block != 0) {
		testflow->flowtable = container_of(f->block, struct nf_flowtable, flow_block);
		gnveth_flowtable = priv->testflow.flowtable;
	} else {
		testflow->flowtable = 0;

	}

	f->unlocked_driver_cb = true;

	switch (type) {
	case TC_SETUP_FT:
		return flow_block_cb_setup_simple(type_data,
						&nveth_block_ft_cb_list,
						nveth_setup_ft_cb,
						testflow, testflow, true);
	default:
		return 0;
	}

	printk("call nveth_testflow_setup_tc\n");

	return 0;
}
static bool ip_has_options(unsigned int thoff)
{
	return thoff != sizeof(struct iphdr);
}

struct flow_offload_tuple* 
nveth_testflow_get_flow(void* pkt, int len, int iifidx, int type)
{
	struct ethhdr *eth = pkt;
	struct iphdr *iph;
	unsigned int thoff;

	struct flow_offload_tuple_rhash *htuple = 0;
	struct flow_offload_tuple tuple;
	struct flow_offload_tuple *ttuple = 0;

	if (!gnveth_flowtable) {
		printk("flowtable is null\n");
		return 0;
	}

	if (eth->h_proto != htons(ETH_P_IP))
		return 0;

	iph = (struct iphdr*)(eth + 1);
	thoff = (iph->ihl * 4);

	if (ip_is_fragment(iph) ||
	    unlikely(ip_has_options(thoff)))
		return 0;

	tuple.src_v4.s_addr	= iph->saddr;
	tuple.dst_v4.s_addr	= iph->daddr;
	tuple.l3proto		= AF_INET;
	tuple.l4proto		= iph->protocol;
	tuple.iifidx		= iifidx;

	if (iph->protocol == IPPROTO_UDP || iph->protocol == IPPROTO_TCP) {
		struct udphdr* uhdr = (struct udphdr*)(iph + 1); 
		tuple.src_port		= uhdr->source;
		tuple.dst_port		= uhdr->dest;
	}

	htuple = flow_offload_lookup(gnveth_flowtable, &tuple);

	if (!htuple) {
		printk("out interface id:%d\n", htuple->tuple.out.ifidx);
		ttuple = &htuple->tuple;
	}

	return ttuple;
}
